/**************************************************
|	File:		GameplayState.cpp
|	Author:		Benjamin Budian
|	Course:		SGD
|	Purpose:	CGameplayState class handles the 
|				gameplay.
**************************************************/


#include "GameplayState.h"

//Wrappers
#include "../SGD Wrappers/CSGD_Direct3D.h"
#include "../SGD Wrappers/CSGD_DirectInput.h"
#include "../SGD Wrappers/CSGD_TextureManager.h"
#include "../SGD Wrappers/CSGD_XAudio2.h"
#include "../SGD Wrappers/CSGD_MessageSystem.h"
#include "../SGD Wrappers/CSGD_EventSystem.h"
#include "../SGD Wrappers/SGD_String.h"
#include "../SGD Wrappers/SGD_Math.h"

//Locals
#include "Game.h"
#include "MainMenuState.h"
#include "MapImage.h"
#include "Entity.h"
#include "EntityManager.h"
#include "DestroyEntityMessage.h"
#include "CreateProjectileMessage.h"
#include "Projectile.h"
#include "MagicProjectile.h"
#include "SoundManager.h"
#include "CreditsState.h"
#include "Armor.h"
#include "Weapon.h"
#include "Object.h"
#include "Player.h"
#include "Enemy.h"
#include "../TinyXML/tinyxml.h"

//System
#include <cassert>
#include <ctime>


/*************************************************/
// GetInstance
//	- store the SINGLETON instance in global memory, 
//	  rather than on the heap
/*static*/ CGameplayState* CGameplayState::GetInstance( void )
{
	static CGameplayState s_Instance;
	return &s_Instance;
}

/*************************************************/
// Constructor
//	- set ALL data members to 'clean' values
CGameplayState::CGameplayState(void)
{
	m_pMS			= nullptr;
	m_pES			= nullptr;
	m_pEM			= nullptr;

	m_pPlayer		= nullptr;
	m_pTestEnemy	= nullptr;
}

/*************************************************/
// Destructor
CGameplayState::~CGameplayState(void){}

/*************************************************/
// Enter
//	- load resources
//	- reset data (optional)
/*virtual*/ void CGameplayState::Enter( void )
{
	
#if 0
	CreateConfigFile();
#endif
	m_nFPS = 0;
	m_nFrames = 0;
	m_fTimer = 0;

	// SGD Wrapper singletons initialized in CGame
	CSGD_TextureManager*	pTM	= CSGD_TextureManager::GetInstance();
	CSGD_XAudio2*			pXA	= CSGD_XAudio2::GetInstance();

	// Initialize the Entity Manager
	m_pEM		= new CEntityManager;

	// Initialize the Message System
	m_pMS = CSGD_MessageSystem::GetInstance();
	m_pMS->InitMessageSystem( &MessageProc );

	// Initialize the Event System
	m_pES = CSGD_EventSystem::GetInstance();

	// Initialize the Sound Manager
	SoundManager::GetInstance()->Initialize();

	// Start Background Music
	pXA->MusicPlaySong(SoundManager::GetInstance()->GetSoundID(BACKGROUND_MUSIC),true);

	m_pBackgroundMap = new CMapImage();

	// Initialize the Game Background
	m_pBackgroundMap->Initialize();

	//Load Configuration file
	if(LoadConfigFile() == false)
	{
		m_pPlayer = new CPlayer();

		dynamic_cast< CPlayer* >(m_pPlayer)->Initialize(0);

		m_pEM->AddEntity(m_pPlayer,0);

		m_nNumEnemies = m_nRoomTotalEnemies = rand() % 10 + 1;

		for(int i = 0; i < m_nNumEnemies; i++){
			CEntity* pEnemy = CreateEnemy();

			m_pEM->AddEntity(pEnemy,2);

			pEnemy->Release();
		}
	}

	

	m_nCursor = RESUME;
	m_bPaused = false;
	m_bGameOverLose = false;
	m_bGameOverWin = false;

}

/*************************************************/
// Exit
//	- unload resources
//	- do NOT shutdown the core wrappers handled by CGame!
/*virtual*/ void CGameplayState::Exit( void )
{
	//Send "GAME_OVER" to all allocated entities
	m_bDebug = false;
	CSGD_EventSystem::GetInstance()->SendEventNow("GAME_OVER");

	delete m_pBackgroundMap;
	m_pBackgroundMap = nullptr;

	m_pPlayer->Release();

	SoundManager::GetInstance()->Shutdown();

	// Clear the event system
	if( m_pES != nullptr )
	{
		m_pES->ShutdownEventSystem();
		m_pES = nullptr;
	}

	// Clear the message system
	if( m_pMS != nullptr )
	{
		m_pMS->ShutdownMessageSystem();
		m_pMS = nullptr;
	}

	// Deallocate the Entity Manager
	if( m_pEM != nullptr )
	{
		m_pEM->RemoveAll();
		delete m_pEM;
		m_pEM = nullptr;
	}
}


/*************************************************/
// Input
//	- handle user input
/*virtual*/ bool CGameplayState::Input( void )
{
	if(m_bGameOverWin == false && m_bGameOverLose == false){
		CSGD_DirectInput* pDI = CSGD_DirectInput::GetInstance();

		if( pDI->KeyPressed( DIK_ESCAPE ) == true )
		{
			m_bPaused = !m_bPaused;
			m_nCursor = RESUME;
			return true;
		}

		if(pDI->KeyPressed(DIK_BACKSPACE) == true){
			m_bDebug = !m_bDebug;
			
			
			CSGD_EventSystem::GetInstance()->SendEvent("DEBUG");
		}


		if(m_bPaused == true){

			if(pDI->KeyPressed( DIK_DOWN ) == true){
				m_nCursor++;

				if(m_nCursor > QUIT)
					m_nCursor = RESUME;
			}
			if(pDI->KeyPressed( DIK_UP ) == true){
				m_nCursor--;

				if(m_nCursor < RESUME)
					m_nCursor = QUIT;
			}

			if(pDI->KeyPressed( DIK_RETURN ) == true){

				switch(m_nCursor){
				case QUIT:
					CGame::GetInstance()->ChangeState( CMainMenuState::GetInstance() );
					break;
				case RESUME:
					m_bPaused = false;
					break;
				}
				return true;	// MUST EXIT THIS FUNCTION!
			}
		}
	}
	return true;
}


/*************************************************/
// Update
//	- update game entities
/*virtual*/ void CGameplayState::Update( float fElapsedTime )
{
	if(m_bPaused == false){
		// Update the entities
		m_pEM->UpdateAll( fElapsedTime );
		m_pEM->CheckAllCollisions( 0, 1 );
		m_pEM->CheckAllCollisions( 0, 2 );
		m_pEM->CheckAllCollisions( 1, 2 );
		m_pEM->CheckAllCollisions( 0, 3 );

		int nRandomChanceAmbient = (rand() % 100000) + 1;
		if(nRandomChanceAmbient == 35){
			CSGD_XAudio2::GetInstance()->SFXPlaySound(SoundManager::GetInstance()->GetSoundID(AMBIENT_EFFECT));
		}

		// Process the messages & events
		m_pMS->ProcessMessages();
		m_pES->ProcessEvents();

		if(m_nNumEnemies == 0 && m_fGameOverTimer <= 0 && m_bGameOverWin == false){
			m_fGameOverTimer = 3.0f;
			m_bGameOverWin = true;
			CSGD_XAudio2::GetInstance()->SFXPlaySound(SoundManager::GetInstance()->GetSoundID(VICTORY));
		}
		if(dynamic_cast< CPlayer* >(m_pPlayer)->GetHealth() <= 0 && m_fGameOverTimer <= 0.0f && m_bGameOverLose == false){
			m_fGameOverTimer = 3.0f;
			m_bGameOverLose = true;
			dynamic_cast< CPlayer* >(m_pPlayer)->GameOver(true);
			CSGD_XAudio2::GetInstance()->SFXPlaySound(SoundManager::GetInstance()->GetSoundID(PLAYER_DEATH));
		}

		if(m_fGameOverTimer >= 0 && (m_bGameOverWin == true || m_bGameOverLose == true)){
			m_fGameOverTimer -= fElapsedTime;
		}

		if(m_fGameOverTimer <= 0 && (m_bGameOverWin == true || m_bGameOverLose == true)){
			CGame::GetInstance()->ChangeState(CCreditsState::GetInstance());
			return;
		}
	}

	m_fTimer += fElapsedTime;
	m_nFrames++;

	if(m_fTimer >= 1.0f){
		m_nFPS = m_nFrames;
		m_nFrames = 0;
		m_fTimer = 0;
	}
}


/*************************************************/
// Render
//	- render game entities
/*virtual*/ void CGameplayState::Render( void )
{
	// Render Map
	m_pBackgroundMap->Render();

	// Render entities
	m_pEM->RenderAll();

	CBitmapFont* pFont = CGame::GetInstance()->GetFont();

	TOSTRINGSTREAM toss;
	
	if(m_bDebug){
		toss << "Number of Entities: " << m_pEM->GetNumEntities() << '\n'
			 << "FPS: " << m_nFPS;
		pFont->Draw(toss.str().c_str(), 20, 100, 0.4f,D3DCOLOR_ARGB(255,0,0,0));

		toss.str(_T(""));
	}

	toss << "Kill All Enemies: " << m_nNumEnemies << " / " << m_nRoomTotalEnemies;

	pFont->Draw(toss.str().c_str(), CGame::GetInstance()->GetScreenWidth()/2 - (strlen("Kill All Enemies")/2)*32, 20,.6f,D3DCOLOR_ARGB(255,255,255,0));

	if(m_bPaused == true)
	{
		CSGD_Direct3D* pD3D = CSGD_Direct3D::GetInstance();

		RECT rPause = { CGame::GetInstance()->GetScreenWidth()/2 - 200, 
			CGame::GetInstance()->GetScreenHeight()/2 - 200, 
			CGame::GetInstance()->GetScreenWidth()/2 + 200, 
			CGame::GetInstance()->GetScreenHeight()/2 + 200 };

		pD3D->DrawRect(rPause,D3DCOLOR_ARGB(127,0,0,0));

		CBitmapFont* pFont = CGame::GetInstance()->GetFont();

		int nCenterOffset = CGame::GetInstance()->GetScreenWidth()/2 - (strlen("Resume")*32)/2;
		pFont->Draw(_T("Resume"),nCenterOffset, rPause.top + 50,1.0f,D3DCOLOR_ARGB(255,255,255,255));

		//nCenterOffset = CGame::GetInstance()->GetScreenWidth()/2 - (strlen("Save Game")*32)/2;
		//pFont->Draw(_T("Save Game"),nCenterOffset, rPause.top + 100,1.0f,D3DCOLOR_ARGB(255,255,255,255));

		//nCenterOffset = CGame::GetInstance()->GetScreenWidth()/2 - (strlen("Load Game")*32)/2;
		//pFont->Draw(_T("Load Game"),nCenterOffset, rPause.top + 100,1.0f,D3DCOLOR_ARGB(255,255,255,255));

		nCenterOffset = CGame::GetInstance()->GetScreenWidth()/2 - (strlen("Quit")*32)/2;
		pFont->Draw(_T("Quit"),nCenterOffset, rPause.top + 100,1.0f,D3DCOLOR_ARGB(255,255,255,255));

		pFont->Draw(_T(">"),rPause.left + 50, (rPause.top + 50) + ( m_nCursor * 50),1.0f,D3DCOLOR_ARGB(255,255,255,255));
	}

	if(m_bGameOverLose == true && m_bPaused == false){
		CSGD_Direct3D* pD3D = CSGD_Direct3D::GetInstance();

		RECT rPause = { CGame::GetInstance()->GetScreenWidth()/2 - 200, 
			CGame::GetInstance()->GetScreenHeight()/2 - 200, 
			CGame::GetInstance()->GetScreenWidth()/2 + 200, 
			CGame::GetInstance()->GetScreenHeight()/2 + 200 };

		pD3D->DrawRect(rPause,D3DCOLOR_ARGB(127,0,0,0));

		CBitmapFont* pFont = CGame::GetInstance()->GetFont();

		int nCenterOffsetX = CGame::GetInstance()->GetScreenWidth()/2 - (strlen("You Lose!!") * 32)/2;
		int nCenterOffsetY = CGame::GetInstance()->GetScreenHeight()/2 - 16;
		pFont->Draw(_T("You Lose!!"), nCenterOffsetX, nCenterOffsetY,1.0f,D3DCOLOR_ARGB(255,255,255,255));
	}

	if(m_bGameOverWin == true && m_bPaused == false){
		CSGD_Direct3D* pD3D = CSGD_Direct3D::GetInstance();

		RECT rPause = { CGame::GetInstance()->GetScreenWidth()/2 - 200, 
			CGame::GetInstance()->GetScreenHeight()/2 - 200, 
			CGame::GetInstance()->GetScreenWidth()/2 + 200, 
			CGame::GetInstance()->GetScreenHeight()/2 + 200 };

		pD3D->DrawRect(rPause,D3DCOLOR_ARGB(127,0,0,0));

		CBitmapFont* pFont = CGame::GetInstance()->GetFont();

		int nCenterOffsetX = CGame::GetInstance()->GetScreenWidth()/2 - (strlen("You Win!!") * 32)/2;
		int nCenterOffsetY = CGame::GetInstance()->GetScreenHeight()/2 - 16;
		pFont->Draw(_T("You Win!!"), nCenterOffsetX, nCenterOffsetY,1.0f,D3DCOLOR_ARGB(255,255,255,255));
	}
}

/*************************************************/
// MessageProc
//	- processes a single message from the Message System
//	- STATIC METHOD
//		- no invoking object!
//		- uses the CGameplayState singleton to access data members
/*static*/ void CGameplayState::MessageProc( IMessage* pMsg )
{
	switch( pMsg->GetMessageID() ){

	case MSG_GAME_OVER:

		break;

	case MSG_CREATE_PROJECTILE:
		{
			CCreateProjectileMessage* pCreateMsg = dynamic_cast< CCreateProjectileMessage* >( pMsg );
			assert( pCreateMsg != nullptr && "CGameplayState::MessageProc - MSG_CREATE_PROJECTILE is not what it appears to be!");

			CGameplayState* self = CGameplayState::GetInstance();

			switch(pCreateMsg->GetType()){
			case NORMAL:
				{
					CEntity* pProjectile = self->CreateProjectile( pCreateMsg->GetOwner(),pCreateMsg->GetDirection());
					CSGD_XAudio2::GetInstance()->SFXPlaySound(SoundManager::GetInstance()->GetSoundID(BOW_SHOT));
					self->m_pEM->AddEntity(pProjectile,1);
					if(self->m_bDebug)
						dynamic_cast< CProjectile* >(pProjectile)->SetDebugMode(true);
					pProjectile->Release();
				}
				break;
			case MAGIC:
				{
					CEntity* pProjectile = self->CreateMagicProjectile( pCreateMsg->GetOwner(),pCreateMsg->GetDirection());
					CSGD_XAudio2::GetInstance()->SFXPlaySound(SoundManager::GetInstance()->GetSoundID(BOW_SHOT));
					self->m_pEM->AddEntity(pProjectile,1);
					pProjectile->Release();
					if(self->m_bDebug)
						dynamic_cast< CMagicProjectile* >(pProjectile)->SetDebugMode(true);
				}
				break;
			}
		}
		break;

	case MSG_DESTROY_ENTITY:
		{
			CDestroyEntityMessage* pDestroyMsg = dynamic_cast<CDestroyEntityMessage*>(pMsg);
			assert( pDestroyMsg != nullptr && "CGame::MessageProc - MSG_DESTROY_ENTITY is not what it appears to be");

			CEntity* pEntity = pDestroyMsg->GetEntity();

			if(pEntity->GetType() == CEntity::ENT_ENEMY){
				/*CSGD_EventSystem::GetInstance()->SendEvent("ENEMY_KILLED",nullptr,CGameplayState::GetInstance()->m_pPlayer)*/;
				dynamic_cast< CPlayer* >(CGameplayState::GetInstance()->m_pPlayer)->UpdateKills();
				CGameplayState::GetInstance()->m_nNumEnemies--;
				int nDropChance = rand() % 100 + 1;
				if(nDropChance <= 50){
					CArmor* pArmor = new CArmor();
					pArmor->SetPosX(pEntity->GetPosX());
					pArmor->SetPosY(pEntity->GetPosY());
					ArmorPieces nArmorType = (ArmorPieces)(rand() % 6);
					pArmor->SetArmorType(nArmorType);
					CGameplayState::GetInstance()->m_pEM->AddEntity(pArmor,3);
					if(CGameplayState::GetInstance()->m_bDebug)
						pArmor->SetDebugMode(true);
					pArmor->Release();
				}
				if (nDropChance > 50 && nDropChance <= 75){
       				Weapon* weapon = new Weapon();
					weapon->Initialize(pEntity->GetPosX(), pEntity->GetPosY(), Weapon::SWORD, CGameplayState::GetInstance()->m_bDebug);
					CGameplayState::GetInstance()->m_pEM->AddEntity(weapon, 3);
					weapon->Release();
				}
				if (nDropChance > 75 && nDropChance <= 100){
					Weapon* weapon = new Weapon();
					weapon->Initialize(pEntity->GetPosX(), pEntity->GetPosY(), Weapon::BOW, CGameplayState::GetInstance()->m_bDebug);
					CGameplayState::GetInstance()->m_pEM->AddEntity(weapon, 3);
					weapon->Release();
				}



			}

			pEntity->Release();

			CGameplayState::GetInstance()->m_pEM->RemoveEntity(pEntity);
		}
		break;
	}
}

CEntity* CGameplayState::CreateEnemy() const {
	CEnemy* pEnemy = new CEnemy();

	CharacterTypes type = (CharacterTypes)(rand() % NUMBER_CHARACTER_TYPES);
	pEnemy->Initialize(type);

	pEnemy->SetPosX( (float)(rand() % (CGame::GetInstance()->GetScreenWidth() - 2*pEnemy->GetWidth()) + pEnemy->GetWidth() ));
	pEnemy->SetPosY( (float)(rand() % (CGame::GetInstance()->GetScreenHeight() - 2* pEnemy->GetHeight()) + pEnemy->GetHeight()) );

	return pEnemy;
}

CEntity* CGameplayState::CreateProjectile(CEntity* pOwner, int nDirection) const {
	CProjectile* projectile = new CProjectile(nDirection);

	projectile->SetOwner(pOwner);

	projectile->SetPosX(pOwner->GetPosX());
	projectile->SetPosY(pOwner->GetPosY());

	switch(nDirection){
	case LEFT:
		projectile->SetVelX(-800);
		break;
	case RIGHT:
		projectile->SetVelX(800);
		break;
	case UP:
		projectile->SetVelY(-800);
		break;
	case DOWN:
		projectile->SetVelY(800);
		break;
	}

	return projectile;
}

CEntity* CGameplayState::CreateMagicProjectile(CEntity* pOwner, int nDirection) const {

	CMagicProjectile* magicProjectile = new CMagicProjectile(0);

	magicProjectile->SetOwner(pOwner);

	magicProjectile->SetPosX(pOwner->GetPosX() + pOwner->GetWidth()/2);
	magicProjectile->SetPosY(pOwner->GetPosY() + pOwner->GetHeight()/2);

	switch(nDirection){
	case LEFT:
		magicProjectile->SetVelX(-500);
		break;
	case RIGHT:
		magicProjectile->SetVelX(500);
		break;
	case UP:
		magicProjectile->SetVelY(-500);
		break;
	case DOWN:
		magicProjectile->SetVelY(500);
		break;
	}

	return magicProjectile;
}

void CGameplayState::CreateConfigFile( void ){
	TiXmlDocument doc;

	TiXmlDeclaration* pDecl = new TiXmlDeclaration("1.0", "utf-8","");

	doc.LinkEndChild(pDecl);

	TiXmlElement* pRoot = new TiXmlElement("configuration");

	doc.LinkEndChild(pRoot);

	TiXmlElement* pPlayer = new TiXmlElement("player");

	pPlayer->SetAttribute("health",100);
	pPlayer->SetDoubleAttribute("fire_timer",1.0);
	pPlayer->SetAttribute("head", 0);
	pPlayer->SetAttribute("hands", 0);
	pPlayer->SetAttribute("feet", 0);
	pPlayer->SetAttribute("legs", 0);
	pPlayer->SetAttribute("shoulder", 0);
	pPlayer->SetAttribute("chest", 0);

	TiXmlElement* pLevel = new TiXmlElement("level");

	pLevel->SetAttribute("num_enemies",-1);

	TiXmlElement* pEnemy = new TiXmlElement("enemy");

	pEnemy->SetAttribute("health",100);
	pEnemy->SetAttribute("armor", 0);
	pEnemy->SetDoubleAttribute("fire_timer", 0.6);

	pRoot->LinkEndChild(pPlayer);
	pRoot->LinkEndChild(pLevel);
	pRoot->LinkEndChild(pEnemy);

	doc.SaveFile("config.xml");

}

bool CGameplayState::LoadConfigFile(){

	TiXmlDocument doc;

	if(doc.LoadFile("config.xml") == false )
		return false;

	TiXmlElement* pRoot = doc.RootElement();

	if(pRoot == nullptr)
		return false;

	//Load Player Configurations
	m_pPlayer = new CPlayer();
	dynamic_cast< CPlayer* >(m_pPlayer)->Initialize(0);

	TiXmlElement* pPlayer = pRoot->FirstChildElement("player");

	int nHealth;
	double fFireTimer;
	int nArmor;

	pPlayer->Attribute("health",&nHealth);
	pPlayer->Attribute("fire_timer",&fFireTimer);

	dynamic_cast< CPlayer* >(m_pPlayer)->SetHealth(nHealth);
	dynamic_cast< CPlayer* >(m_pPlayer)->SetFireTimer((float)fFireTimer);


	pPlayer->Attribute("head",&nArmor);
	if(nArmor == 1)
		dynamic_cast< CPlayer* >(m_pPlayer)->SetArmor(HEAD);
	pPlayer->Attribute("hands",&nArmor);
	if(nArmor == 1)
		dynamic_cast< CPlayer* >(m_pPlayer)->SetArmor(HANDS);
	pPlayer->Attribute("feet",&nArmor);
	if(nArmor == 1)
		dynamic_cast< CPlayer* >(m_pPlayer)->SetArmor(FEET);
	pPlayer->Attribute("legs",&nArmor);
	if(nArmor == 1)
		dynamic_cast< CPlayer* >(m_pPlayer)->SetArmor(LEGS);
	pPlayer->Attribute("shoulder",&nArmor);
	if(nArmor == 1)
		dynamic_cast< CPlayer* >(m_pPlayer)->SetArmor(SHOULDER);
	pPlayer->Attribute("chest",&nArmor);
	if(nArmor == 1)
		dynamic_cast< CPlayer* >(m_pPlayer)->SetArmor(CHEST);


	m_pEM->AddEntity(m_pPlayer,0);
	////////////////////////////

	pPlayer = pPlayer->NextSiblingElement("level");

	//Load Level Configurations
	int nNumEnemies;

	pPlayer->Attribute("num_enemies",&nNumEnemies);

	if(nNumEnemies < 0)
		m_nNumEnemies = m_nRoomTotalEnemies = rand() % 12 + 8;
	else
		m_nNumEnemies = nNumEnemies;

	pPlayer = pPlayer->NextSiblingElement("enemy");

	pPlayer->Attribute("health",&nHealth);
	pPlayer->Attribute("armor",&nArmor);
	pPlayer->Attribute("fire_timer",&fFireTimer);

	for(int i = 0; i < m_nNumEnemies; i++){
		CEntity* pEnemy = CreateEnemy();

		if(nHealth > 0)
			dynamic_cast< CEnemy*>(pEnemy)->SetHealth(nHealth);
		if(nArmor > 0)
			dynamic_cast< CEnemy*>(pEnemy)->SetArmor(nArmor);
		if(fFireTimer > 0.0f)
			dynamic_cast< CEnemy*>(pEnemy)->SetFireTimer((float)fFireTimer);

		m_pEM->AddEntity(pEnemy,2);

		pEnemy->Release();
	}


	return true;

}













